// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FBL_INTRUSIVE_HASH_TABLE_H_
#define FBL_INTRUSIVE_HASH_TABLE_H_

#include <zircon/assert.h>
#include <fbl/intrusive_container_utils.h>
#include <fbl/intrusive_pointer_traits.h>
#include <fbl/intrusive_single_list.h>
#include <fbl/macros.h>

#include <utility>

namespace fbl {

// Fwd decl of sanity checker class used by tests.
namespace tests {
namespace intrusive_containers {
class HashTableChecker;
}  // namespace intrusive_containers
}  // namespace tests

namespace internal {
inline constexpr size_t kDefaultNumBuckets = 37;
}  // namespace internal

// DefaultHashTraits defines a default implementation of traits used to
// define the hash function for a hash table.
//
// At a minimum, a class or a struct which is to be used to define the
// hash traits of a hashtable must define...
//
// GetHash : A static method which take a constant reference to an instance of
//           the container's KeyType and returns an instance of the container's
//           HashType representing the hashed value of the key.  The value must
//           be on the range from [0, Container::kNumBuckets - 1]
//
// DefaultHashTraits generates a compliant implementation of hash traits taking
// its KeyType, ObjType, HashType and NumBuckets from template parameters.
// Users of DefaultHashTraits only need to implement a static method of ObjType
// named GetHash which takes a const reference to a KeyType and returns a
// HashType.  The default implementation will automatically mod by the number of
// buckets given in the template parameters.  If the user's hash function
// already automatically guarantees that the returned hash value will be in the
// proper range, he/she should implement their own hash traits to avoid the
// extra div/mod operation.
template <typename KeyType, typename ObjType, typename HashType, HashType kNumBuckets>
struct DefaultHashTraits {
  static_assert(std::is_unsigned_v<HashType>, "HashTypes must be unsigned integers");
  static HashType GetHash(const KeyType& key) {
    return static_cast<HashType>(ObjType::GetHash(key) % kNumBuckets);
  }
};

template <typename _KeyType, typename _PtrType, typename _BucketType = SinglyLinkedList<_PtrType>,
          typename _HashType = size_t, _HashType _NumBuckets = internal::kDefaultNumBuckets,
          typename _KeyTraits = DefaultKeyedObjectTraits<
              _KeyType, typename internal::ContainerPtrTraits<_PtrType>::ValueType>,
          typename _HashTraits = DefaultHashTraits<
              _KeyType, typename internal::ContainerPtrTraits<_PtrType>::ValueType, _HashType,
              _NumBuckets>>
class HashTable {
 private:
  // Private fwd decls of the iterator implementation.
  template <typename IterTraits>
  class iterator_impl;
  struct iterator_traits;
  struct const_iterator_traits;

 public:
  // Pointer types/traits
  using PtrType = _PtrType;
  using PtrTraits = internal::ContainerPtrTraits<PtrType>;
  using ValueType = typename PtrTraits::ValueType;

  // Key types/traits
  using KeyType = _KeyType;
  using KeyTraits = _KeyTraits;

  // Hash types/traits
  using HashType = _HashType;
  using HashTraits = _HashTraits;

  // Bucket types/traits
  using BucketType = _BucketType;
  using NodeTraits = typename BucketType::NodeTraits;

  // Declarations of the standard iterator types.
  using iterator = iterator_impl<iterator_traits>;
  using const_iterator = iterator_impl<const_iterator_traits>;

  // An alias for the type of this specific HashTable<...> and its test sanity checker.
  using ContainerType =
      HashTable<_KeyType, _PtrType, _BucketType, _HashType, _NumBuckets, _KeyTraits, _HashTraits>;
  using CheckerType = ::fbl::tests::intrusive_containers::HashTableChecker;

  // The number of buckets should be a nice prime such as 37, 211, 389 unless
  // The hash function is really good. Lots of cheap hash functions have
  // hidden periods for which the mod with prime above 'mostly' fixes.
  static constexpr HashType kNumBuckets = _NumBuckets;

  // Hash tables only support constant order erase if their underlying bucket
  // type does.
  static constexpr bool SupportsConstantOrderErase = BucketType::SupportsConstantOrderErase;
  static constexpr bool SupportsConstantOrderSize = true;
  static constexpr bool IsAssociative = true;
  static constexpr bool IsSequenced = false;

  static_assert(kNumBuckets > 0, "Hash tables must have at least one bucket");
  static_assert(std::is_unsigned_v<HashType>, "HashTypes must be unsigned integers");

  constexpr HashTable() {}
  ~HashTable() { ZX_DEBUG_ASSERT(PtrTraits::IsManaged || is_empty()); }

  // Standard begin/end, cbegin/cend iterator accessors.
  iterator begin() { return iterator(this, iterator::BEGIN); }
  const_iterator begin() const { return const_iterator(this, const_iterator::BEGIN); }
  const_iterator cbegin() const { return const_iterator(this, const_iterator::BEGIN); }

  iterator end() { return iterator(this, iterator::END); }
  const_iterator end() const { return const_iterator(this, const_iterator::END); }
  const_iterator cend() const { return const_iterator(this, const_iterator::END); }

  // make_iterator : construct an iterator out of a reference to an object.
  iterator make_iterator(ValueType& obj) {
    HashType ndx = GetHash(KeyTraits::GetKey(obj));
    return iterator(this, ndx, buckets_[ndx].make_iterator(obj));
  }

  void insert(const PtrType& ptr) { insert(PtrType(ptr)); }
  void insert(PtrType&& ptr) {
    ZX_DEBUG_ASSERT(ptr != nullptr);
    KeyType key = KeyTraits::GetKey(*ptr);
    BucketType& bucket = GetBucket(key);

    // Duplicate keys are disallowed.  Debug assert if someone tries to to
    // insert an element with a duplicate key.  If the user thought that
    // there might be a duplicate key in the HashTable already, he/she
    // should have used insert_or_find() instead.
    ZX_DEBUG_ASSERT(FindInBucket(bucket, key).IsValid() == false);

    bucket.push_front(std::move(ptr));
    ++count_;
  }

  // insert_or_find
  //
  // Insert the element pointed to by ptr if it is not already in the
  // HashTable, or find the element that the ptr collided with instead.
  //
  // 'iter' is an optional out parameter pointer to an iterator which
  // will reference either the newly inserted item, or the item whose key
  // collided with ptr.
  //
  // insert_or_find returns true if there was no collision and the item was
  // successfully inserted, otherwise it returns false.
  //
  bool insert_or_find(const PtrType& ptr, iterator* iter = nullptr) {
    return insert_or_find(PtrType(ptr), iter);
  }

  bool insert_or_find(PtrType&& ptr, iterator* iter = nullptr) {
    ZX_DEBUG_ASSERT(ptr != nullptr);
    KeyType key = KeyTraits::GetKey(*ptr);
    HashType ndx = GetHash(key);
    auto& bucket = buckets_[ndx];
    auto bucket_iter = FindInBucket(bucket, key);

    if (bucket_iter.IsValid()) {
      if (iter)
        *iter = iterator(this, ndx, bucket_iter);
      return false;
    }

    bucket.push_front(std::move(ptr));
    ++count_;
    if (iter)
      *iter = iterator(this, ndx, bucket.begin());
    return true;
  }

  // insert_or_replace
  //
  // Find the element in the hashtable with the same key as *ptr and replace
  // it with ptr, then return the pointer to the element which was replaced.
  // If no element in the hashtable shares a key with *ptr, simply add ptr to
  // the hashtable and return nullptr.
  //
  PtrType insert_or_replace(const PtrType& ptr) { return insert_or_replace(PtrType(ptr)); }

  PtrType insert_or_replace(PtrType&& ptr) {
    ZX_DEBUG_ASSERT(ptr != nullptr);
    KeyType key = KeyTraits::GetKey(*ptr);
    HashType ndx = GetHash(key);
    auto& bucket = buckets_[ndx];
    auto orig = PtrTraits::GetRaw(ptr);

    PtrType replaced = bucket.replace_if(
        [key](const ValueType& other) -> bool {
          return KeyTraits::EqualTo(key, KeyTraits::GetKey(other));
        },
        std::move(ptr));

    if (orig == PtrTraits::GetRaw(replaced)) {
      bucket.push_front(std::move(replaced));
      count_++;
      return nullptr;
    }

    return replaced;
  }

  iterator find(const KeyType& key) {
    HashType ndx = GetHash(key);
    auto& bucket = buckets_[ndx];
    auto bucket_iter = FindInBucket(bucket, key);

    return bucket_iter.IsValid() ? iterator(this, ndx, bucket_iter) : iterator(this, iterator::END);
  }

  const_iterator find(const KeyType& key) const {
    HashType ndx = GetHash(key);
    const auto& bucket = buckets_[ndx];
    auto bucket_iter = FindInBucket(bucket, key);

    return bucket_iter.IsValid() ? const_iterator(this, ndx, bucket_iter)
                                 : const_iterator(this, const_iterator::END);
  }

  PtrType erase(const KeyType& key) {
    BucketType& bucket = GetBucket(key);

    PtrType ret = internal::KeyEraseUtils<BucketType, KeyTraits>::erase(bucket, key);
    if (ret != nullptr)
      --count_;

    return ret;
  }

  PtrType erase(const iterator& iter) {
    if (!iter.IsValid())
      return PtrType(nullptr);

    return direct_erase(buckets_[iter.bucket_ndx_], *iter);
  }

  PtrType erase(ValueType& obj) { return direct_erase(GetBucket(obj), obj); }

  // clear
  //
  // Clear out the all of the hashtable buckets.  For managed pointer types,
  // this will release all references held by the hashtable to the objects
  // which were in it.
  void clear() {
    for (auto& e : buckets_)
      e.clear();
    count_ = 0;
  }

  // clear_unsafe
  //
  // Perform a clear_unsafe on all buckets and reset the internal count to
  // zero.  See comments in fbl/intrusive_single_list.h
  // Think carefully before calling this!
  void clear_unsafe() {
    static_assert(PtrTraits::IsManaged == false,
                  "clear_unsafe is not allowed for containers of managed pointers");

    for (auto& e : buckets_)
      e.clear_unsafe();

    count_ = 0;
  }

  size_t size() const { return count_; }
  bool is_empty() const { return count_ == 0; }

  // erase_if
  //
  // Find the first member of the hash table which satisfies the predicate
  // given by 'fn' and erase it from the list, returning a referenced pointer
  // to the removed element.  Return nullptr if no member satisfies the
  // predicate.
  template <typename UnaryFn>
  PtrType erase_if(UnaryFn fn) {
    if (is_empty())
      return PtrType(nullptr);

    for (HashType i = 0; i < kNumBuckets; ++i) {
      auto& bucket = buckets_[i];
      if (!bucket.is_empty()) {
        PtrType ret = bucket.erase_if(fn);
        if (ret != nullptr) {
          --count_;
          return ret;
        }
      }
    }

    return PtrType(nullptr);
  }

  // find_if
  //
  // Find the first member of the hash table which satisfies the predicate
  // given by 'fn' and return an iterator to it.  Return end() if no member
  // satisfies the predicate.
  template <typename UnaryFn>
  const_iterator find_if(UnaryFn fn) const {
    for (auto iter = begin(); iter.IsValid(); ++iter)
      if (fn(*iter))
        return iter;

    return end();
  }

  template <typename UnaryFn>
  iterator find_if(UnaryFn fn) {
    for (auto iter = begin(); iter.IsValid(); ++iter)
      if (fn(*iter))
        return iter;

    return end();
  }

 private:
  // The traits of a non-const iterator
  struct iterator_traits {
    using RefType = typename PtrTraits::RefType;
    using RawPtrType = typename PtrTraits::RawPtrType;
    using IterType = typename BucketType::iterator;

    static IterType BucketBegin(BucketType& bucket) { return bucket.begin(); }
    static IterType BucketEnd(BucketType& bucket) { return bucket.end(); }
  };

  // The traits of a const iterator
  struct const_iterator_traits {
    using RefType = typename PtrTraits::ConstRefType;
    using RawPtrType = typename PtrTraits::ConstRawPtrType;
    using IterType = typename BucketType::const_iterator;

    static IterType BucketBegin(const BucketType& bucket) { return bucket.cbegin(); }
    static IterType BucketEnd(const BucketType& bucket) { return bucket.cend(); }
  };

  // The shared implementation of the iterator
  template <class IterTraits>
  class iterator_impl {
   public:
    iterator_impl() {}
    iterator_impl(const iterator_impl& other) {
      hash_table_ = other.hash_table_;
      bucket_ndx_ = other.bucket_ndx_;
      iter_ = other.iter_;
    }

    iterator_impl& operator=(const iterator_impl& other) {
      hash_table_ = other.hash_table_;
      bucket_ndx_ = other.bucket_ndx_;
      iter_ = other.iter_;
      return *this;
    }

    bool IsValid() const { return iter_.IsValid(); }
    bool operator==(const iterator_impl& other) const { return iter_ == other.iter_; }
    bool operator!=(const iterator_impl& other) const { return iter_ != other.iter_; }

    // Prefix
    iterator_impl& operator++() {
      if (!IsValid())
        return *this;
      ZX_DEBUG_ASSERT(hash_table_);

      // Bump the bucket iterator and go looking for a new bucket if the
      // iterator has become invalid.
      ++iter_;
      advance_if_invalid_iter();

      return *this;
    }

    iterator_impl& operator--() {
      // If we have never been bound to a HashTable instance, the we had
      // better be invalid.
      if (!hash_table_) {
        ZX_DEBUG_ASSERT(!IsValid());
        return *this;
      }

      // Back up the bucket iterator.  If it is still valid, then we are done.
      --iter_;
      if (iter_.IsValid())
        return *this;

      // If the iterator is invalid after backing up, check previous
      // buckets to see if they contain any nodes.
      while (bucket_ndx_) {
        --bucket_ndx_;
        auto& bucket = GetBucket(bucket_ndx_);
        if (!bucket.is_empty()) {
          iter_ = --IterTraits::BucketEnd(bucket);
          ZX_DEBUG_ASSERT(iter_.IsValid());
          return *this;
        }
      }

      // Looks like we have backed up past the beginning.  Update the
      // bookkeeping to point at the end of the last bucket.
      bucket_ndx_ = kNumBuckets - 1;
      iter_ = IterTraits::BucketEnd(GetBucket(bucket_ndx_));

      return *this;
    }

    // Postfix
    iterator_impl operator++(int) {
      iterator_impl ret(*this);
      ++(*this);
      return ret;
    }

    iterator_impl operator--(int) {
      iterator_impl ret(*this);
      --(*this);
      return ret;
    }

    typename PtrTraits::PtrType CopyPointer() const { return iter_.CopyPointer(); }
    typename IterTraits::RefType operator*() const { return iter_.operator*(); }
    typename IterTraits::RawPtrType operator->() const { return iter_.operator->(); }

   private:
    friend ContainerType;
    using IterType = typename IterTraits::IterType;

    enum BeginTag { BEGIN };
    enum EndTag { END };

    iterator_impl(const ContainerType* hash_table, BeginTag)
        : hash_table_(hash_table), bucket_ndx_(0), iter_(IterTraits::BucketBegin(GetBucket(0))) {
      advance_if_invalid_iter();
    }

    iterator_impl(const ContainerType* hash_table, EndTag)
        : hash_table_(hash_table),
          bucket_ndx_(kNumBuckets - 1),
          iter_(IterTraits::BucketEnd(GetBucket(kNumBuckets - 1))) {}

    iterator_impl(const ContainerType* hash_table, HashType bucket_ndx, const IterType& iter)
        : hash_table_(hash_table), bucket_ndx_(bucket_ndx), iter_(iter) {}

    BucketType& GetBucket(HashType ndx) {
      return const_cast<ContainerType*>(hash_table_)->buckets_[ndx];
    }

    void advance_if_invalid_iter() {
      // If the iterator has run off the end of it's current bucket, then
      // check to see if there are nodes in any of the remaining buckets.
      if (!iter_.IsValid()) {
        while (bucket_ndx_ < (kNumBuckets - 1)) {
          ++bucket_ndx_;
          auto& bucket = GetBucket(bucket_ndx_);

          if (!bucket.is_empty()) {
            iter_ = IterTraits::BucketBegin(bucket);
            ZX_DEBUG_ASSERT(iter_.IsValid());
            break;
          } else if (bucket_ndx_ == (kNumBuckets - 1)) {
            iter_ = IterTraits::BucketEnd(bucket);
          }
        }
      }
    }

    const ContainerType* hash_table_ = nullptr;
    HashType bucket_ndx_ = 0;
    IterType iter_;
  };

  PtrType direct_erase(BucketType& bucket, ValueType& obj) {
    PtrType ret = internal::DirectEraseUtils<BucketType>::erase(bucket, obj);

    if (ret != nullptr)
      --count_;

    return ret;
  }

  static typename BucketType::iterator FindInBucket(BucketType& bucket, const KeyType& key) {
    return bucket.find_if([key](const ValueType& other) -> bool {
      return KeyTraits::EqualTo(key, KeyTraits::GetKey(other));
    });
  }

  static typename BucketType::const_iterator FindInBucket(const BucketType& bucket,
                                                          const KeyType& key) {
    return bucket.find_if([key](const ValueType& other) -> bool {
      return KeyTraits::EqualTo(key, KeyTraits::GetKey(other));
    });
  }

  // The test framework's 'checker' class is our friend.
  friend CheckerType;

  // Iterators need to access our bucket array in order to iterate.
  friend iterator;
  friend const_iterator;

  // Hash tables may not currently be copied, assigned or moved.
  DISALLOW_COPY_ASSIGN_AND_MOVE(HashTable);

  BucketType& GetBucket(const KeyType& key) { return buckets_[GetHash(key)]; }
  BucketType& GetBucket(const ValueType& obj) { return GetBucket(KeyTraits::GetKey(obj)); }

  static HashType GetHash(const KeyType& obj) {
    HashType ret = HashTraits::GetHash(obj);
    ZX_DEBUG_ASSERT((ret >= 0) && (ret < kNumBuckets));
    return ret;
  }

  size_t count_ = 0UL;
  BucketType buckets_[kNumBuckets];
};

// TaggedHashTable<> is intended for use with ContainableBaseClasses<>.
//
// For an easy way to allow instances of your class to live in multiple
// intrusive containers at once, simply derive from
// ContainableBaseClasses<YourContainables<PtrType, TagType>...> and then use
// this template instead of HashTable<> as the container, passing the same tag
// type you used earlier as the third parameter.
//
// See comments on ContainableBaseClasses<> in fbl/intrusive_container_utils.h
// for more details.
//
template <typename KeyType, typename PtrType, typename TagType,
          typename BucketType = TaggedSinglyLinkedList<PtrType, TagType>,
          typename HashType = size_t, HashType NumBuckets = internal::kDefaultNumBuckets,
          typename KeyTraits = DefaultKeyedObjectTraits<
              KeyType, typename internal::ContainerPtrTraits<PtrType>::ValueType>,
          typename HashTraits =
              DefaultHashTraits<KeyType, typename internal::ContainerPtrTraits<PtrType>::ValueType,
                                HashType, NumBuckets>>
using TaggedHashTable =
    HashTable<KeyType, PtrType, BucketType, HashType, NumBuckets, KeyTraits, HashTraits>;

// Explicit declaration of constexpr storage.
#define HASH_TABLE_PROP(_type, _name)                                                   \
  template <typename KeyType, typename PtrType, typename BucketType, typename HashType, \
            HashType NumBuckets, typename KeyTraits, typename HashTraits>               \
  constexpr _type                                                                       \
      HashTable<KeyType, PtrType, BucketType, HashType, NumBuckets, KeyTraits, HashTraits>::_name

HASH_TABLE_PROP(HashType, kNumBuckets);
HASH_TABLE_PROP(bool, SupportsConstantOrderErase);
HASH_TABLE_PROP(bool, SupportsConstantOrderSize);
HASH_TABLE_PROP(bool, IsAssociative);
HASH_TABLE_PROP(bool, IsSequenced);

#undef HASH_TABLE_PROP

}  // namespace fbl

#endif  // FBL_INTRUSIVE_HASH_TABLE_H_
